import collections
import datetime

from coin.base.book.types import TradeSide
from coin.exchange.base.order_gateway import OrderSide
import coin.strategy.mm.fastfeature.feature_feed_converter as ffcv
import coin.strategy.mm.fastfeature.feed_manager as fmgr
from coin.strategy.mm.base.bookmap import BookMap
import coin.strategy.mm.fastfeature.linear_model_factory as lmf
import coin.strategy.mm.tool.archive_base as abase
from coin.strategy.hk_lm.model_fast_profile.profiles import ModelFastProfile

import coin.proto.archive_flag_pb2 as afpb


class StatPrinter(object):
  def __init__(self):
    self.trade_cnt_ = collections.defaultdict(int)
    self.pq_sum_ = collections.defaultdict(float)

  def on_trade(self, product, trade):
    self.trade_cnt_[product] += 1
    self.pq_sum_[product] += trade.price * trade.qty
    if trade.side == TradeSide.TRADE_BUY_SIDE:
      sidestr = "B"
    elif trade.side == TradeSide.TRADE_SELL_SIDE:
      sidestr = "S"
    else:
      sidestr = "U"
    print(
        "%.0f [T] %s %s %.4f %.4f %s %s" %
        (trade.timestamp,
         product.exchange,
         product.subscription_symbol if hasattr(product, 'subscription_symbol') else product.symbol,
         trade.price,
         trade.qty,
         sidestr,
         "T" if trade.has_more_trade else "F"))

  def on_book(self, product, book):
    askarr = book.get_ask_array(1)
    bidarr = book.get_bid_array(1)
    print(
        "%.0f [B] %s %s %.4f %.4f %.4f %.4f" %
        (book.timestamp,
         product.exchange,
         product.subscription_symbol if hasattr(product, 'subscription_symbol') else product.symbol,
         bidarr[0][0],
         bidarr[0][1],
         askarr[0][0],
         askarr[0][1]))


class SignalDumpStrategy(object):
  def __init__(self, model_profile):
    self._feed_products = tuple(model_profile.product_map.keys())
    self.product = model_profile.product
    self._feed_converter = model_profile.converter_factory\
        .create_feed_converter(ffcv.FeatureFeedCacheConverter)
    self._feed_manager = fmgr.FeedManager(self.on_book,
                                          self.on_trade, {},
                                          feedspec=model_profile.converter_factory.feedspec)
    self._model = model_profile.model
    self._book = BookMap([self.product])
    self._signals = []

    self._feed_converter.register_symbols(self._model)
    self._trade_pindex = self._feed_converter.get_index(self.product)
    self._sprinter = StatPrinter()

  def on_book_reset(self, book_builder_name, book_builder):
    self._feed_manager.set_book_reset(self._feed_products, book_builder_name, book_builder)

  def on_trade(self, product, trade):
    if self._feed_converter.update_trade(product, trade, self._model):
      self._sprinter.on_trade(product, trade)
      self.try_fire(self.product, trade.timestamp)

  def on_book(self, product, book):
    if product == self.product:
      self._book.set_book(product, book)
    if self._feed_converter.update_book(product, book, self._model):
      self._sprinter.on_book(product, book)
      self.try_fire(self.product, book.timestamp)

  def try_fire(self, product, timestamp):
    ret_proto = self._feed_converter.trigger(product, self._model)
    buy_model_output = None
    sell_model_output = None
    if ret_proto is not None:
      if len(ret_proto.buy_model) > 0:
        buy_model_tag = ret_proto.buy_model[0].tag
        buy_model_output = ret_proto.buy_model[0].signal
      if len(ret_proto.sell_model) > 0:
        sell_model_tag = ret_proto.sell_model[0].tag
        sell_model_output = ret_proto.sell_model[0].signal
    if buy_model_output:
      print(f"{timestamp} buy-signal  {buy_model_tag} {buy_model_output}")
      self._signals.append({
          'timestamp': timestamp,
          'order_side': OrderSide.BUY,
          'order_price': self._book.get_book(self.product).ask0().price + buy_model_output,
          'agg_price': self._book.get_book(self.product).ask0().price,
          'signal': buy_model_output
      })
    if sell_model_output:
      print(f"{timestamp} sell-signal {sell_model_tag} {sell_model_output}")
      self._signals.append({
          'timestamp': timestamp,
          'order_side': OrderSide.SELL,
          'order_price': self._book.get_book(self.product).bid0().price - sell_model_output,
          'agg_price': self._book.get_book(self.product).bid0().price,
          'signal': sell_model_output
      })


def get_profile(profile_name, start_time):
  trade_subreq = {
      'exchange': 'Okex',
  }
  return ModelFastProfile(profile_name=profile_name,
                          trade_subreq=trade_subreq,
                          model_output_is_bps=profile_name.endswith('bps'),
                          datetime_override=start_time)


def dump_lm_signal(profile_name, trading_date, time_range):
  add_hour, hours = time_range.split("-")
  add_hour = float(add_hour)
  hours = float(hours) - add_hour
  start_time = trading_date + datetime.timedelta(hours=add_hour)
  model_profile = get_profile(profile_name, start_time)
  dumpstrategy = SignalDumpStrategy(model_profile)
  baseflags = afpb.BaseFlagProto(trading_date=trading_date.strftime("%Y%m%d"),
                                 time_range=time_range)
  fsflags = abase.get_feed_subscription_flags()
  abase.run_from_archive_base_with_feed_sub(baseflags=baseflags,
                                            feed_sub_flags_proto=fsflags,
                                            feed_sub_request=model_profile.feed_subreq,
                                            on_book_reset=dumpstrategy.on_book_reset)

  return dumpstrategy._signals


def get_lm_product(profile_name, trading_date):
  _, targets = lmf.get_product_map('profile/%s/linear_alpha.json' % profile_name, trading_date)
  assert len(targets) == 1
  return targets[0]
